import { Keyboard } from "@/editor/framework/event/Keyboard";
import { MOUSE, Quaternion, Vector2, Vector3 } from "three";


const STATE = {
    NONE: - 1,
    ROTATE: 0,
    DOLLY: 1,
    PAN: 2,
    TOUCH_ROTATE: 3,
    TOUCH_PAN: 4,
    TOUCH_DOLLY_PAN: 5,
    TOUCH_DOLLY_ROTATE: 6
};

/**
 * @description 摄像机控制类，
 * @author songmy
 */
export default class LockCameraCtrl implements IDispose {

    protected readonly EPS = 0.000001;

    protected _camera: any;

    protected _domElement: HTMLElement;

    protected _enabled: boolean;

    protected _pointers: any[];

    protected _pointerPositions: any;

    protected _state: number;

    protected _panStart: Vector2;

    protected _panEnd: Vector2;

    protected _panDelta: Vector2;

    protected _dollyStart: Vector2;

    protected _dollyEnd: Vector2;

    protected _dollyDelta: Vector2;

    protected _scale: number;

    protected _minZoom: number;

    protected _maxZoom: number;

    protected _zoomSpeed: number;

    protected _panSpeed: number;

    protected _target: Vector3;

    protected _panOffset: Vector3;

    protected _screenSpacePanning: boolean;

    protected _minDistance: number;

    protected _maxDistance: number;
    /** 按键移动的像素 */
    protected _keyPanSpeed: number;

    private _contextmenuCallback: any;

    private _pointerdownCallback: any;

    private _pointercancelCallback: any;

    private _mousewheelCallback: any;

    private _pointmoveCallback: any;

    private _pointerupCallback: any;

    protected mouseButtons = {
        LEFT: MOUSE.ROTATE,
        MIDDLE: MOUSE.DOLLY,
        RIGHT: MOUSE.PAN
    }

    get camera(): any {
        return this._camera;
    }

    set camera(camera: any) {
        this._camera = camera;
    }

    get enabled(): boolean {
        return this._enabled;
    }

    set enabled(value: boolean) {
        this._enabled = value;
    }

    get target(): Vector3 {
        return this._target;
    }

    set target(value: Vector3) {
        this._target = value;
    }

    constructor(camera: any, document: HTMLElement) {

        console.log('create orthcameractrl')
        this._camera = camera;
        this._domElement = document;
        this._enabled = true;
        this._pointers = [];
        this._pointerPositions = {};
        this._state = STATE.NONE;
        this._scale = 1;
        this._minZoom = 0;
        this._maxZoom = Infinity;
        this._zoomSpeed = 1.0;
        this._panSpeed = 1.0;
        this._keyPanSpeed = 5.0;

        this._minDistance = 0;
        this._maxDistance = Infinity;

        this._panStart = new Vector2();
        this._panEnd = new Vector2();
        this._panDelta = new Vector2();

        this._dollyStart = new Vector2();
        this._dollyEnd = new Vector2();
        this._dollyDelta = new Vector2();

        this._target = new Vector3();

        this._panOffset = new Vector3();

        this._screenSpacePanning = false;

        this.addEve();
        this.update();
    }

    dispose(): void {
        this._camera = null;
        this._domElement = null;

        this._pointers = [];
        this._pointerPositions = null;

        this._panStart = null;
        this._panEnd = null;
        this._panDelta = null;

        this._dollyStart = null;
        this._dollyEnd = null;
        this._dollyDelta = null;

        this._target = null;
        this._panOffset = null;

        this._contextmenuCallback = null;
        this._pointerdownCallback = null;
        this._pointercancelCallback = null;
        this._mousewheelCallback = null;
        this._pointmoveCallback = null;
        this._pointerupCallback = null;
    }

    protected removeEve(): void {
        this._domElement.removeEventListener('contextmenu', this._contextmenuCallback);
        this._domElement.removeEventListener('pointerdown', this._pointerdownCallback);
        this._domElement.removeEventListener('pointercancel', this._pointercancelCallback);
        this._domElement.removeEventListener('wheel', this._mousewheelCallback);
        this._domElement.removeEventListener('pointermove', this._pointmoveCallback);
        this._domElement.removeEventListener('pointerup', this._pointerupCallback);
    }

    protected addEve(): void {

        this._contextmenuCallback = (event: any) => {
            this.onContextMenu(event);
        }
        this._pointerdownCallback = (event: any) => {
            this.onPointerDown(event);
        }
        this._pointercancelCallback = (event: any) => {
            this.onPointerCancel(event);
        }
        this._mousewheelCallback = (event: any) => {
            this.onMouseWheel(event);
        }
        this._pointmoveCallback = (event: any) => {
            this.onPointerMove(event);
        }
        this._pointerupCallback = (event: any) => {
            this.onPointerUp(event);
        }

        this._domElement.addEventListener('contextmenu', this._contextmenuCallback);
        this._domElement.addEventListener('pointerdown', this._pointerdownCallback);
        this._domElement.addEventListener('pointercancel', this._pointercancelCallback);
        this._domElement.addEventListener('wheel', this._mousewheelCallback, {
            passive: false
        });
    }

    protected onContextMenu(event: any): void {
        if (this._enabled === false) return;
        event.preventDefault();
    }

    protected onPointerDown(event: any): void {
        if (this._enabled === false) return;
        if (this._pointers.length === 0) {
            this._domElement.setPointerCapture(event.pointerId);
            this._domElement.addEventListener('pointermove', this._pointmoveCallback);
            this._domElement.addEventListener('pointerup', this._pointerupCallback);
        }
        this.addPointer(event);
        this.onMouseDown(event);
    }

    protected addPointer(event: any): void {
        this._pointers.push(event);
    }

    protected removePointer(event: any): void {
        delete this._pointerPositions[event.pointerId];
        for (let i = 0; i < this._pointers.length; i++) {
            if (this._pointers[i].pointerId == event.pointerId) {
                this._pointers.splice(i, 1);
                return;
            }
        }
    }

    protected onPointerCancel(event: any): void {
        this.removePointer(event);
    }

    protected onMouseWheel(event: any): void {
        if (this._enabled === false || this._state != STATE.NONE) return;
        event.preventDefault();
        this.handleMouseWheel(event);
    }

    protected onPointerMove(event: any): void {
        if (this._enabled === false) return;
        this.onMouseMove(event);
    }

    protected onPointerUp(event: any): void {

        // BaseUtls.changeMouseStyle(MouseStyle.DEDFAULT);

        this.removePointer(event);
        if (this._pointers.length === 0) {
            this._domElement.releasePointerCapture(event.pointerId);
            this._domElement.removeEventListener('pointermove', this._pointmoveCallback);
            this._domElement.removeEventListener('pointerup', this._pointerupCallback);
        }
        this._state = STATE.NONE;
    }

    protected onMouseDown(event: any): void {

        let mouseAction = event.button == 0 ? this.mouseButtons.LEFT :
            event.button == 1 ? this.mouseButtons.MIDDLE :
                event.button == 2 ? this.mouseButtons.RIGHT : -1;
        switch (mouseAction) {
            case MOUSE.DOLLY:
                this.handleMouseDownPan(event);
                this._state = STATE.PAN;
                break;
            case MOUSE.ROTATE:
                // this.handleMouseDownPan(event);
                // this._state = STATE.PAN;
                break;
            case MOUSE.PAN:
                this.handleMouseDownPan(event);
                this._state = STATE.PAN;
                break;
            default:
                this._state = STATE.NONE;
                break;
        }
    }

    protected onMouseMove(event: any): void {
        if (this._enabled === false) return;
        switch (this._state) {
            case STATE.ROTATE:
                // this.handleMouseMovePan(event);
                break;
            case STATE.DOLLY:
                this.handleMouseMoveDolly(event);
                break;
            case STATE.PAN:
                this.handleMouseMovePan(event);
                break;
        }
    }

    protected handleMouseDownPan(event: any): void {
        this._panStart.set(event.clientX, event.clientY);
    }

    /** 左右移动 */
    protected panLeft(distance: number, objectMatrix: any): void {
        let v = new Vector3();
        v.setFromMatrixColumn(objectMatrix, 0);

        v.multiplyScalar(- distance);
        this._panOffset.add(v);
    }

    /** 上下移动 */
    protected panUp(distance: number, objectMatrix: any): void {
        let v = new Vector3();
        if (this._screenSpacePanning === true) {
            v.setFromMatrixColumn(objectMatrix, 1);
        }
        else {
            v.setFromMatrixColumn(objectMatrix, 0);
            v.crossVectors(this._camera.up, v);
        }

        v.multiplyScalar(distance);
        this._panOffset.add(v);
    }

    /** 前后移动 */
    protected panForward(distance: number, objectMatrix: any): void {
        let v = new Vector3();
        v.setFromMatrixColumn(objectMatrix, 0);
        // 向量叉积
        v.crossVectors(this._camera.up, v);
        v.multiplyScalar(- distance);
        this._panOffset.add(v);
    }

    protected handleMouseMoveDolly(event: any): void {

        this._dollyEnd.set(event.clientX, event.clientY);
        this._dollyDelta.subVectors(this._dollyEnd, this._dollyStart);

        if (this._dollyDelta.y > 0) {

            this.dollyOut(this.getZoomScale());

        } else if (this._dollyDelta.y < 0) {

            this.dollyIn(this.getZoomScale());
        }

        this._dollyStart.copy(this._dollyEnd);
        this.update();
    }

    protected pan(deltaX: number, deltaY: number, isForward: boolean = false): void {

        let element = this._domElement;
        if (this._camera.isOrthographicCamera) {

            // 正交相机
            if (isForward) {
                this.panForward(deltaX * (this._camera.top - this._camera.bottom) / this._camera.zoom / element.clientWidth, this._camera.matrix);
            }
            else {
                this.panLeft(deltaX * (this._camera.right - this._camera.left) / this._camera.zoom / element.clientWidth, this._camera.matrix);
            }
        }
        else {
            // 相机类型出错
            console.warn('WARNING: OrthCameraCtrl.ts encountered an unknown camera type - pan disabled.');
        }
    }

    protected handleMouseMovePan(event: any): void {

        // BaseUtls.changeMouseStyle(MouseStyle.TRANSLATE);

        this._panEnd.set(event.clientX, event.clientY);
        this._panDelta.subVectors(this._panEnd, this._panStart).multiplyScalar(this._panSpeed);
        this.pan(this._panDelta.x, 0);
        this.pan(-this._panDelta.y, 0, true);
        this._panStart.copy(this._panEnd);
        this.update();
    }

    protected getZoomScale(): number {
        return Math.pow(0.95, this._zoomSpeed);
    }

    protected handleMouseWheel(event: any): void {

        // BaseUtls.changeMouseStyle(MouseStyle.SCALE);

        if (event.deltaY < 0) {
            this.dollyIn(this.getZoomScale());
        }
        else if (event.deltaY > 0) {
            this.dollyOut(this.getZoomScale());
        }
        this.update();
        // BIM.timer.once(300, this, this.resetMouse);
    }

    private resetMouse(): void {
        // BIM.timer.clear(this, this.resetMouse);
        // BaseUtls.changeMouseStyle(MouseStyle.DEDFAULT);
    }

    update(): void {

        let offset = new Vector3(); // camera.up 轨道轴 
        let quat = new Quaternion().setFromUnitVectors(this._camera.up, new Vector3(0, 1, 0));
        let quatInverse = quat.clone().invert();

        const position = this._camera.position;
        offset.copy(position).sub(this._target); // 旋转偏移到“y轴向上”空间
        offset.applyQuaternion(quat); // 从z轴到y轴的角度

        this._target.add(this._panOffset);
        // 添加左右上下的球形位置
        offset.applyQuaternion(quatInverse);
        position.copy(this._target).add(offset);
        this._camera.lookAt(this._target);

        this._panOffset.set(0, 0, 0);

        this._scale = 1;
    }

    protected dollyIn(dollyScale: number): void {
        if (this._camera.isPerspectiveCamera) {
            this._scale *= dollyScale;
        }
        else if (this._camera.isOrthographicCamera) {
            this._camera.zoom = Math.max(this._minZoom, Math.min(this._maxZoom, this._camera.zoom / dollyScale));
            this._camera.updateProjectionMatrix();
        }
        else {
            console.warn('BIMAI_WARING: OrthCameraCtrl.ts encountered an unknown camera type -function:dollyIn()')
        }
    }

    protected dollyOut(dollyScale: number): void {
        if (this._camera.isPerspectiveCamera) {
            this._scale /= dollyScale;
        }
        else if (this._camera.isOrthographicCamera) {
            this._camera.zoom = Math.max(this._minZoom, Math.min(this._maxZoom, this._camera.zoom * dollyScale));
            this._camera.updateProjectionMatrix();
        }
        else {
            console.warn('BIMAI_WARING: OrthCameraCtrl.ts encountered an unknown camera type -function:dollyOut()')
        }
    }

    protected onKeyDown(event: any): void {
        if (this._enabled === false) return;
        this.handleKeyDown(event);
    }

    protected handleKeyDown(event: any): void {
        let needsUpdate = false;

        switch (event.keyCode) {
            case Keyboard.W:
                this.pan(-this._keyPanSpeed, 0, true);
                needsUpdate = true;
                break;
            case Keyboard.S:
                if (!event.ctrlKey) {
                    this.pan(this._keyPanSpeed, 0, true);
                    needsUpdate = true;
                }
                break;
            case Keyboard.A:
                this.pan(this._keyPanSpeed, 0);
                needsUpdate = true;
                break;
            case Keyboard.D:
                this.pan(- this._keyPanSpeed, 0);
                needsUpdate = true;
                break;
        }

        if (needsUpdate) {
            //防止浏览器在光标键上滚动
            event.preventDefault();
            this.update();
        }
    }


}